package com.example.mybatis.mapper;

import com.example.mybatis.config.Config;
import com.example.mybatis.constant.MapperAttrConst;
import com.example.mybatis.constant.MapperForeachConst;
import com.example.mybatis.constant.MapperSqlType;
import com.example.mybatis.constant.MapperTypeConst;
import com.example.mybatis.mapper.component.MapperForeachProperty;
import com.example.mybatis.support.replace.ISqlReplace;
import com.example.mybatis.support.replace.SqlReplaceResult;
import com.example.mybatis.support.replace.impl.IncludeRefSqlReplace;
import com.example.mybatis.utils.XmlUtils;
import com.github.houbb.heaven.util.lang.StringUtil;
import com.github.houbb.heaven.util.lang.reflect.ClassUtil;
import com.github.houbb.heaven.util.util.CollectionUtil;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.dom4j.tree.DefaultText;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class MapperRegister {
    private final static Map<String, MapperMethod> MAPPER_METHOD_MAP = new ConcurrentHashMap<>();
    private final Config config;

    public MapperRegister(Config config) {
        this.config = config;
    }

    public MapperRegister addMapper(final String mapperPath) {
        MapperClass mapperClass = buildMapperData(mapperPath);
        String namespace = mapperClass.getNamespace();
        Class<?> clazz = ClassUtil.getClass(namespace);

        this.addMapper(clazz, mapperClass);

        return this;
    }

    private MapperRegister addMapper(final Class<?> clazz, final MapperClass mapperClass) {
        String className = clazz.getName();
        List<MapperMethod> mapperMethods = mapperClass.getMapperMethods();
        for (MapperMethod mapperMethod : mapperMethods) {
            String methodName = mapperMethod.getMethodName();
            String key = className + "#" + methodName;
            MAPPER_METHOD_MAP.put(key, mapperMethod);
        }
        return this;
    }

    private MapperClass buildMapperData(final String mapperPath) {
        MapperClass mapperClass = new MapperClass();
        Element element = XmlUtils.getRoot(mapperPath);
        String namespace = element.attributeValue(MapperAttrConst.NAMESPACE);
        List<?> elements = element.elements();
        Map<String, Method> methodMap = buildMethodMap(namespace);
        List<MapperMethod> mapperMethods = new ArrayList<>();
        List<MapperSqlTemplateSon> mapperSqlTemplates = new ArrayList<>();
        Map<String, List<MapperResultItemTemplate>> resultHashMap = new HashMap<>();
        for (Object item : elements) {
            Element elementData = (Element) item;
            MapperMethod mapperMethod = new MapperMethod();
            mapperMethod.setRefClass(mapperClass);
            String type = elementData.getName();
            mapperMethod.setType(type);
            String id = elementData.attributeValue(MapperAttrConst.ID);
            if (MapperTypeConst.SQL.equals(type)) {
                MapperSqlTemplateSon mapperSqlTemplate = new MapperSqlTemplateSon();
                mapperSqlTemplate.setId(id);
                mapperSqlTemplate.setSql(elementData.getTextTrim());
                mapperSqlTemplates.add(mapperSqlTemplate);
            } else if (MapperTypeConst.INSERT.equals(type) || MapperTypeConst.UPDATE.equals(type) || MapperTypeConst.DELETE.equals(type)) {
                mapperMethod.setMethodName(id);
                Method method = methodMap.get(id);
                mapperMethod.setMethod(method);
                String paramType = elementData.attributeValue(MapperAttrConst.PARAM_TYPE);
                if (StringUtil.isNotBlank(paramType)) {
                    mapperMethod.setParamType(ClassUtil.getClass(config.getTypeAlias(paramType)));
                }
                //构建sql信息
                List<MapperSqlTemplate> sqlTemplates = buildSqlItems(elementData);
                mapperMethod.setSqlItemList(sqlTemplates);
                //这个是暂时的sql
                mapperMethod.setSql(elementData.getTextTrim());
                mapperMethods.add(mapperMethod);
            } else if (MapperTypeConst.SELECT.equals(type)) {
                mapperMethod.setMethodName(id);
                Method method = methodMap.get(id);
                mapperMethod.setMethod(method);
                String paramType = elementData.attributeValue(MapperAttrConst.PARAM_TYPE);
                String resultType = elementData.attributeValue(MapperAttrConst.RESULT_TYPE);
                String resultMap = elementData.attributeValue(MapperAttrConst.RESULT_MAP);
                if (StringUtil.isNotEmpty(paramType)) {
                    // 入参可能不存在
                    mapperMethod.setParamType(ClassUtil.getClass(config.getTypeAlias(paramType)));
                }
                if (StringUtil.isNotEmpty(resultType)) {
                    // 出参可能不存在
                    mapperMethod.setResultType(ClassUtil.getClass(config.getTypeAlias(resultType)));
                }

                mapperMethod.setResultMap(resultMap);

                // 构建 sql 信息
                List<MapperSqlTemplate> sqlItemList = buildSqlItems(element);
                mapperMethod.setSqlItemList(sqlItemList);
                // 这个是暂时的 sql
                mapperMethod.setSql(element.getTextTrim());

                mapperMethods.add(mapperMethod);
            } else if (MapperTypeConst.RESULT_MAP.equals(type)) {
                List<MapperResultItemTemplate> resultItemTemplates = buildResultItemTemplate(elementData);
                resultHashMap.put(id, resultItemTemplates);
            }
        }
        mapperClass.setNamespace(namespace);
        mapperClass.setMapperMethods(mapperMethods);
        mapperClass.setSqlTemplateList(mapperSqlTemplates);
        mapperClass.setResultMapMapping(resultHashMap);

        //替换模版
        replaceSqlTemplate(mapperClass);

        return mapperClass;
    }

    /**
     * 替换掉对应的sql模版
     * 1. sql 模板列表为空，直接返回。
     * <p>
     * 2. 遍历每一条 sql，遍历其实是否包含 include
     * 根据 refId 找到对应的 sql，然后替换内容。
     * <p>
     * 重新设置 sql 为替换后的 sql
     *
     * @param mapperClass
     */
    private void replaceSqlTemplate(final MapperClass mapperClass) {
        List<MapperSqlTemplateSon> sqlTemplates = mapperClass.getSqlTemplateList();
        List<MapperMethod> mapperMethods = mapperClass.getMapperMethods();
        if (CollectionUtil.isEmpty(sqlTemplates)) {
            return;
        }
        if (CollectionUtil.isEmpty(mapperMethods)) {
            return;
        }
        for (MapperMethod mapperMethod : mapperMethods) {
            ISqlReplace sqlReplace = new IncludeRefSqlReplace();
            //入参
            SqlReplaceResult sqlReplaceResult = SqlReplaceResult.newInstance().mapperMethod(mapperMethod);
            //出参
            sqlReplace.replace(sqlReplaceResult);
        }
    }

    private List<MapperResultItemTemplate> buildResultItemTemplate(Element elementData) {
        List<?> itemDocs = elementData.elements(MapperTypeConst.RESULT);
        List<MapperResultItemTemplate> mapItems = new ArrayList<>(itemDocs.size());

        for (Object doc : itemDocs) {
            DefaultElement elem = (DefaultElement) doc;

            MapperResultItemTemplate mapItem = new MapperResultItemTemplate();
            mapItem.setColumn(elem.attributeValue(MapperAttrConst.COLUMN));
            mapItem.setProperty(elem.attributeValue(MapperAttrConst.PROPERTY));
            mapItem.setJavaType(elem.attributeValue(MapperAttrConst.JAVA_TYPE));
            mapItem.setJdbcType(elem.attributeValue(MapperAttrConst.JDBC_TYPE));
            mapItem.setTypeHandler(elem.attributeValue(MapperAttrConst.TYPE_HANDLER));
            mapItems.add(mapItem);
        }

        return mapItems;
    }

    private List<MapperSqlTemplate> buildSqlItems(Element elementData) {
        List<?> contentList = elementData.content();
        List<MapperSqlTemplate> sqlTemplates = new ArrayList<>();
        for (Object content : contentList) {
            MapperSqlTemplate sqlTemplate = new MapperSqlTemplate();
            if (content instanceof DefaultText) {
                DefaultText defaultText = (DefaultText) content;
                //纯文本是直接拼接的
                sqlTemplate.setReadyForSql(true);
                sqlTemplate.setType(MapperSqlType.TEXT);
                sqlTemplate.setSql(defaultText.getText().trim());
            } else if (content instanceof DefaultElement) {
                DefaultElement defaultElement = (DefaultElement) content;
                String type = defaultElement.getName();
                if (MapperTypeConst.INCLUDE.equals(type)) {
                    //后期引入动态，暂时只考虑简单的refID
                    //后期
                    sqlTemplate.setType(MapperSqlType.INCLUDE);
                    sqlTemplate.setRefId(defaultElement.attributeValue(MapperAttrConst.REF_ID));
                }
                if (MapperTypeConst.IF.equals(type)) {
                    sqlTemplate.setType(MapperSqlType.IF);
                    sqlTemplate.setSql(defaultElement.getText().trim());
                    sqlTemplate.setTestCondition(defaultElement.attributeValue(MapperAttrConst.TEST));
                }
                if (MapperTypeConst.FOREACH.equals(type)) {
                    initForeaachSqlItem(sqlTemplate, defaultElement);
                }
            }
            sqlTemplates.add(sqlTemplate);
        }
        return sqlTemplates;
    }

    /**
     * 初始化 foreach 元素
     * <p>
     * 1. list
     * 2. set
     * 3. map
     *
     * @param sqlTemplate    元素信息
     * @param defaultElement xml 配置
     * @since 0.0.17
     */
    private void initForeaachSqlItem(MapperSqlTemplate sqlTemplate, DefaultElement defaultElement) {
        sqlTemplate.setType(MapperSqlType.FOREACH);
        sqlTemplate.setSql(defaultElement.getText().trim());
        //直接foreach完整的sql构建粗来，替换好固定的元素名称
        String open = defaultElement.attributeValue(MapperAttrConst.OPEN);
        String close = defaultElement.attributeValue(MapperAttrConst.CLOSE);
        String separator = defaultElement.attributeValue(MapperAttrConst.SEPARATOR);
        String item = defaultElement.attributeValue(MapperAttrConst.ITEM);
        String index = defaultElement.attributeValue(MapperAttrConst.INDEX);
        String collection = defaultElement.attributeValue(MapperAttrConst.COLLECTION);
        //默认值
        if (StringUtil.isEmpty(open)) {
            open = MapperForeachConst.OPEN;
        }
        if (StringUtil.isEmpty(close)) {
            close = MapperForeachConst.CLOSE;
        }
        if (StringUtil.isEmpty(separator)) {
            separator = MapperForeachConst.SEPARATOR;
        }
        if (StringUtil.isEmpty(item)) {
            item = MapperForeachConst.ITEM;
        }
        if (StringUtil.isEmpty(index)) {
            index = MapperForeachConst.INDEX;
        }
        if (StringUtil.isEmpty(collection)) {
            collection = MapperForeachConst.COLLECTION;
        }
        MapperForeachProperty mapperForeachProperty = MapperForeachProperty.newInstance().open(open).close(close).index(index).item(item).collection(collection).separator(separator);
        sqlTemplate.setForeachProperty(mapperForeachProperty);
    }

    private Map<String, Method> buildMethodMap(String namespace) {
        //    Map<String,Method>  methodMap   = new HashMap<>();
        Class clazz = ClassUtil.getClass(namespace);
        Method[] methods = clazz.getMethods();
        Map<String, Method> methodMap = new HashMap<>(methods.length);
        for (Method method : methods) {
            methodMap.put(method.getName(), method);
        }
        return methodMap;
    }


    /**
     * 获取Mapper方法的信息
     *
     * @param clazz
     * @param methodName
     * @return
     */
    public MapperMethod getMapperMethod(Class clazz, String methodName) {
        String key = clazz.getName() + "#" + methodName;
        return MAPPER_METHOD_MAP.get(key);
    }
}
